package app.species.ANN;

import java.io.Serializable;

import app.species.CreatureContext;
import app.species.Mutable;
import app.species.SpeciesSim;

import ec.util.MersenneTwisterFast;

//this is the entire neural network for one agent, it starts like a soup of somas and axons
//it creates and then specifies whats connected to what from reading the genome
//activates the whole network, contains input and output nodes
//responsible for neuron to neuron interaction
public abstract class Brain implements Serializable, Mutable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	// ///////////////////////////////////////////
	// static vars will be set in the run.conf
	// ///////////////////////////////////////////
	private static double AXON_WEIGHT_MUT_RATE = 0.1;
	private static double AXON_MODE_MUT_RATE = 0.01;
	private static double NEW_SOMA_MUT_RATE = 0.01;
	private static double NEW_SOMA_INPUT_RATE = 0.05;
	private static double NEW_SOMA_OUTPUT_RATE = 0.04;
	private static double NEW_SOMA_HIDDEN_RATE = 0.91;
	private static double NEW_AXON_MUT_RATE = 0.01;

	// the compatibility weights
	private static double c1 = 1.0;
	private static double c2 = 1.0;
	private static double c3 = 0.4;
	private static double compThreshold = 1.0;

	private static final int numActions = CreatureContext.numDefaultActions();

	private static float REMOVE_SOMA_MUT_RATE = 0.01f;
	private static final float REMOVE_DISABLE_AXON_RATE = 0.10f;
	private static int somaInnovNum = 0;
	private static int axonInnovNum = 0;

	// ///////////////////////////////////////////
	// STATUS ATTRIBUTES
	// ///////////////////////////////////////////

	/**
	 * Every axon. Axons link neurons with a weight.
	 */
	private Axon[] allAxons;

	/**
	 * Every neuron of the brain. Each neuron has axons to other neurons
	 */
	private Neuron[] neurons;
	/**
	 * cached array of input neurons will improve performance to set new Input
	 */
	private InputNeuron[] inputNeurons;

	/**
	 * cached array of output neurons will improve performance if we want to
	 * compute output
	 */
	private OutputNeuron[] outputNeurons;

	// we'll keep track of this to make it easier to
	// retrieve the outputs and inputs
	private int numOutputs = 0; 
	private int numInputs = 0;
	private int maxOutputID = 0;
	private int maxInputID = 0;

	// //////////////////////////////////////////////
	// DNA
	// will be used to merge with other
	// parent, mutate and generate child brain
	// //////////////////////////////////////
	private SomaChromosome sChrome;
	private AxonChromosome aChrome;
	private MersenneTwisterFast random;

	public Brain(SomaChromosome nodes, AxonChromosome edges, MersenneTwisterFast random) {

		int maxSomaNum, maxAxonNum;

		maxSomaNum = maxAxonNum = 0;

		this.sChrome = nodes;
		this.aChrome = edges;
		this.random = random;

		this.allAxons = new Axon[edges.length()];

		for (int i = 0; i < this.allAxons.length; i++) {
			this.allAxons[i] = new Axon(edges.getGene(i).getID(), edges.getGene(i).getWeight());
			if (maxAxonNum < edges.getGene(i).getID())
				maxAxonNum = edges.getGene(i).getID();
		}

		this.neurons = new Neuron[nodes.length()];
		this.numOutputs = 0;

		// loop through, and create a neuron for each soma
		for (int i = 0; i < nodes.length(); i++) {

			SomaGene sGene = nodes.getGene(i);
			Soma soma = new Soma(sGene.getID());
			int numInCount, numOutCount;

			if (maxSomaNum < sGene.getID())
				maxSomaNum = sGene.getID();

			numInCount = numOutCount = 0;

			// get a count of the number of input/output axons this soma has
			for (int j = 0; j < edges.length(); j++) {

				AxonGene aGene = edges.getGene(j);

				// skip over it if it's disabled
				if (aGene.getDisabled())
					continue;

				if (aGene.getEndID() == sGene.getID())
					numInCount++;
				if (aGene.getStartID() == sGene.getID())
					numOutCount++;

			}

			Axon[] incoming = new Axon[numInCount];
			Axon[] outgoing = new Axon[numOutCount];
			numInCount = numOutCount = 0;

			for (int j = 0; j < edges.length(); j++) {

				AxonGene aGene = edges.getGene(j);

				// skip over it if it's disabled
				if (aGene.getDisabled())
					continue;

				if (aGene.getStartID() == sGene.getID())
					outgoing[numOutCount++] = this.allAxons[j];
				if (aGene.getEndID() == sGene.getID())
					incoming[numInCount++] = this.allAxons[j];

			}

			switch (sGene.getType()) {
			case SomaGene.INPUT:
				this.neurons[i] = new InputNeuron(soma,sGene.getUsageID(), incoming, outgoing);
				this.numInputs++;
				maxInputID = Math.max(maxInputID,sGene.getUsageID());
				break;
			case SomaGene.HIDDEN:
				this.neurons[i] = new HiddenNeuron(soma, incoming, outgoing);
				break;
			case SomaGene.OUTPUT:
				this.neurons[i] = new OutputNeuron(soma,sGene.getUsageID(), incoming, outgoing);
				this.numOutputs++;
				maxOutputID = Math.max(maxOutputID,sGene.getUsageID());
				break;
			case SomaGene.BIAS:
				this.neurons[i] = new BiasNeuron(soma, incoming, outgoing);
				break;
			}

		}
		// we know amount of input and output
		// cache input and output neurons for performance issues
		inputNeurons = new InputNeuron[numInputs];
		outputNeurons = new OutputNeuron[numOutputs];

		int inCounter = 0, outCounter = 0;
		for (int i = 0; i < this.neurons.length; i++) {
			if (this.neurons[i] instanceof InputNeuron) {
				inputNeurons[inCounter++] = (InputNeuron) this.neurons[i];
			} else if (this.neurons[i] instanceof OutputNeuron) {
				outputNeurons[outCounter++] = (OutputNeuron) this.neurons[i];
			}
		}

		Brain.updateSomaInnovNum(maxSomaNum + 1);
		Brain.updateAxonInnovNum(maxAxonNum + 1);

	}

	// input array must be the same size as the number of inputs to network, or
	// bigger
	protected void setInput(double[] in) {
		final int countUpTo = Math.min(in.length, this.inputNeurons.length);
		for (int i = 0; i < countUpTo; i++) {
			inputNeurons[i].setInput(in[inputNeurons[i].getUsageID()]);
		}
	}

	// this has to know the number of output neurons, else i will have to do
	// something dynamically
	// before returning the array
	protected double[] getOutput() {
		double[] out = new double[maxOutputID+1];
		for (int i = 0; i < this.outputNeurons.length; i++) {
			out[outputNeurons[i].getUsageID()] += this.outputNeurons[i].soma.output;
		}
		return out;
	}

	// moves all signals one time tick
	protected void activate() {

		for (int i = 0; i < this.neurons.length; i++) {
			this.neurons[i].fire();
		}

		for (int i = 0; i < this.neurons.length; i++) {
			if (this.neurons[i].soma.isActive()) {
				this.neurons[i].setOutAxonsActive();
			}
		}

		// sets all inputs at time (t-1) to the outputs at time t
		for (int i = 0; i < this.allAxons.length; i++) {
			this.allAxons[i].fire();
		}

	}

	/**
	 * Uses this and that to create a new offspring The crossover takes place
	 * like described in the NEAT paper Returns either an AdamBrain or
	 * DavidBrain, depending on what types are passed to it
	 */
	public Mutable merge(Mutable theOne, Mutable theOther, SpeciesSim sim) {
		// Brain _this = (Brain) theOne;
		Brain that = (Brain) theOther;

		/**
		 * TODO: BRAIN can grow, but not shrink Implement a method to kill some
		 * neurons
		 */
		// Brain newBrain;
		SomaChromosome sChromeNew = new SomaChromosome();
		AxonChromosome aChromeNew = new AxonChromosome();
		SomaGene thisSomaGene, thatSomaGene, newSomaGene;
		AxonGene thisAxonGene, thatAxonGene, newAxonGene;
		int thisIndex, thatIndex;

		// create the soma chromosome by just getting each unique soma gene from
		// the parent brains
		thisIndex = thatIndex = 0;
		while (thisIndex < this.sChrome.length() && thatIndex < that.sChrome.length()) {

			thisSomaGene = this.sChrome.getGene(thisIndex);
			thatSomaGene = that.sChrome.getGene(thatIndex);

			if (thisSomaGene.getID() == thatSomaGene.getID()) {

				newSomaGene = new SomaGene(thisSomaGene.getID(), thisSomaGene.getType(),thisSomaGene.getUsageID());
				thisIndex++;
				thatIndex++;

			} else if (thisSomaGene.getID() < thatSomaGene.getID()) {

				newSomaGene = new SomaGene(thisSomaGene.getID(), thisSomaGene.getType(),thisSomaGene.getUsageID());
				thisIndex++;

			} else {

				newSomaGene = new SomaGene(thatSomaGene.getID(), thatSomaGene.getType(),thatSomaGene.getUsageID());
				thatIndex++;

			}

			sChromeNew.addGene(newSomaGene);

		}

		/* NOTE: ONLY ONE OF THE TWO FOR LOOPS WILL GET EXECUTED */
		// add the soma genes at the end of this brain
		for (int i = thisIndex; i < this.sChrome.length(); i++) {
			thisSomaGene = this.sChrome.getGene(i);
			newSomaGene = new SomaGene(thisSomaGene.getID(), thisSomaGene.getType(),thisSomaGene.getUsageID());
			sChromeNew.addGene(newSomaGene);
		}

		// add the soma genes at the end of that brain
		for (int i = thatIndex; i < that.sChrome.length(); i++) {
			thatSomaGene = that.sChrome.getGene(i);
			newSomaGene = new SomaGene(thatSomaGene.getID(), thatSomaGene.getType(),thatSomaGene.getUsageID());
			sChromeNew.addGene(newSomaGene);
		}

		/*
		 * create the axon chromosome by getting all axons from the two parents
		 * if each parent has the axon, then average the weights
		 */
		thisIndex = thatIndex = 0;
		while (thisIndex < this.aChrome.length() && thatIndex < that.aChrome.length()) {

			thisAxonGene = this.aChrome.getGene(thisIndex);
			thatAxonGene = that.aChrome.getGene(thatIndex);

			if (thisAxonGene.getID() == thatAxonGene.getID()) {

				double w = (thisAxonGene.getWeight() + thatAxonGene.getWeight()) / 2.0;
				boolean dis = false;
				boolean mode = true;

				if (thisAxonGene.getDisabled() == thatAxonGene.getDisabled()) {
					dis = thisAxonGene.getDisabled();
				} else {
					dis = this.random.nextBoolean();
				}

				if (thisAxonGene.getMode() == thatAxonGene.getMode()) {
					mode = thisAxonGene.getMode();
				} else {
					mode = this.random.nextBoolean();
				}

				newAxonGene = new AxonGene(thisAxonGene.getID(), thisAxonGene.getStartID(), thisAxonGene.getEndID(), w,
						dis, mode);
				thisIndex++;
				thatIndex++;

			} else if (thisAxonGene.getID() < thatAxonGene.getID()) {

				newAxonGene = new AxonGene(thisAxonGene.getID(), thisAxonGene.getStartID(), thisAxonGene.getEndID(),
						thisAxonGene.getWeight(), thisAxonGene.getDisabled(), thisAxonGene.getMode());
				thisIndex++;

			} else {

				newAxonGene = new AxonGene(thatAxonGene.getID(), thatAxonGene.getStartID(), thatAxonGene.getEndID(),
						thatAxonGene.getWeight(), thatAxonGene.getDisabled(), thatAxonGene.getMode());
				thatIndex++;

			}

			aChromeNew.addGene(newAxonGene);

		}

		/* NOTE: ONLY ONE OF THE TWO FOR LOOPS WILL GET EXECUTED */
		// add the axon genes at the end of this brain
		for (int i = thisIndex; i < this.aChrome.length(); i++) {
			thisAxonGene = this.aChrome.getGene(i);
			newAxonGene = new AxonGene(thisAxonGene.getID(), thisAxonGene.getStartID(), thisAxonGene.getEndID(),
					thisAxonGene.getWeight(), thisAxonGene.getDisabled(), thisAxonGene.getMode());
			aChromeNew.addGene(newAxonGene);
		}

		// add the axon genes at the end of that brain
		for (int i = thatIndex; i < that.aChrome.length(); i++) {
			thatAxonGene = that.aChrome.getGene(i);
			newAxonGene = new AxonGene(thatAxonGene.getID(), thatAxonGene.getStartID(), thatAxonGene.getEndID(),
					thatAxonGene.getWeight(), thatAxonGene.getDisabled(), thatAxonGene.getMode());
			aChromeNew.addGene(newAxonGene);
		}

		// remove some of the still disabled Axons!
		for (int i = 0; i < aChromeNew.length(); i++) {
			AxonGene tmp = aChromeNew.getGene(i);
			if (tmp.getDisabled() && random.nextBoolean(REMOVE_DISABLE_AXON_RATE)) {
				aChromeNew.removeGene(i--);

			}
		}

		// recombination is complete, now mutate

		if (this instanceof AdamBrain) {
			return new AdamBrain(sChromeNew, aChromeNew, this.random);
		}
		// else {
		// return new DavidBrain(sChromeNew, aChromeNew, this.random);
		// }
		else
			throw new RuntimeException("unknown type of brain" + this.getClass().getName());

	}

	@Override
	public Mutable mutate(float probality, float impact, SpeciesSim sim) {

		SomaChromosome sChromeNew = sChrome;
		AxonChromosome aChromeNew = aChrome;

		if (this.random.nextBoolean()) {
			mutateRemoveSoma(sChromeNew, aChromeNew, random);
		}

		if (this.random.nextBoolean()) {
			mutateAxonWeights(aChromeNew, random);
		}
		if (this.random.nextBoolean()) {
			mutateNewSoma(sChromeNew, aChromeNew, random);
		}
		if (this.random.nextBoolean()) {
			mutateNewAxon(sChromeNew, aChromeNew, random);
		}
		if (this instanceof AdamBrain) {
			return new AdamBrain(sChromeNew, aChromeNew, this.random);
		} else
			throw new RuntimeException("unknown type of brain" + this.getClass().getName());
	}

	private static void updateSomaInnovNum(int n) {

		if (Brain.somaInnovNum < n)
			Brain.somaInnovNum = n;

	}

	private static void updateAxonInnovNum(int n) {

		if (Brain.axonInnovNum < n)
			Brain.axonInnovNum = n;

	}

	private static void mutateAxonWeights(AxonChromosome aChr, MersenneTwisterFast random) {

		// double w;

		// TODO: mutate AxonWeight by some factor? like 1.05 to add 5 % ?
		/**
		 * Problem is: Gaussian is fixed size. What is if weight is -50?
		 * Gaussian will have like no impac?
		 */
		for (int i = 0; i < aChr.length(); i++) {

			if (random.nextBoolean(Brain.AXON_WEIGHT_MUT_RATE)) {
				double d = random.nextGaussian();
				aChr.getGene(i).addWeight(d);
			}
			if (random.nextBoolean(Brain.AXON_MODE_MUT_RATE)) {
				// double d = random.nextGaussian();
				aChr.getGene(i).setMode(random.nextBoolean());
			}

		}

	}

	// create a new soma by inserting it inbetween two already connected somas
	private static void mutateRemoveSoma(SomaChromosome sChr, AxonChromosome aChr, MersenneTwisterFast random) {

		// TODO: too hard ... if we remove a soma, keep some axons alive
		// (connect incoming with some outgoing)
		// for every soma, there is a REMOVE_SOMA_MUT_RATE chance, that a soma
		// will disappear
		for (int i = 0; i < sChr.length(); i++) {
			if (random.nextDouble() < Brain.REMOVE_SOMA_MUT_RATE) {

				if (sChr.getGene(i).getType() == SomaGene.INPUT) {
					continue;
				}
				if (sChr.getGene(i).getType() == SomaGene.OUTPUT) {
					continue;
				}

				// REMOVE EVERY INCOMING and OUTGOING AXON for that soma
				int id = sChr.getGene(i).getID();
				for (int k = 0; k < aChr.length(); k++) {
					AxonGene a = aChr.getGene(k);
					if (a.getEndID() == id || a.getStartID() == id) {
						aChr.removeGene(k--);
					}
				}

				// REMOVE THAT SOMA
				sChr.removeGene(i--);
			}
		}

	}

	// create a new soma by inserting it inbetween two already connected somas
	private static void mutateNewSoma(SomaChromosome sChr, AxonChromosome aChr, MersenneTwisterFast random) {

		// for every soma, there is a NEW_SOMA_MUT_RATE chance, that a new soma
		// will appear
		// conclusion: Big brains will mutate more than little brains
		// but the mutation impact should be the same.
		for (int i = 0; i < sChr.length(); i++) {
			if (random.nextDouble() < Brain.NEW_SOMA_MUT_RATE) {

				int index;
				SomaGene newSomaGene;
				AxonGene oldAxonGene, newAxonGene1, newAxonGene2;

				// find an axon that is not disabled
				do {
					index = random.nextInt(aChr.length());
				} while (aChr.getGene(index).getDisabled());

				oldAxonGene = aChr.getGene(index);

				double type = random.nextDouble();
				// NEW INPUT
				if (type < NEW_SOMA_INPUT_RATE) {
					newSomaGene = new SomaGene(Brain.somaInnovNum++, SomaGene.INPUT,random.nextInt(SomaGene.MAX_INPUT_USAGE_ID));
					newAxonGene1 = new AxonGene(Brain.axonInnovNum++, newSomaGene.getID(), oldAxonGene.getEndID(),
							oldAxonGene.getWeight());
					newAxonGene2 = new AxonGene(Brain.axonInnovNum++, newSomaGene.getID(), oldAxonGene.getStartID(),
							oldAxonGene.getWeight());

				} else
				// NEW OUTPUT
				if (type < NEW_SOMA_INPUT_RATE + NEW_SOMA_OUTPUT_RATE) {
					newSomaGene = new SomaGene(Brain.somaInnovNum++, SomaGene.OUTPUT,random.nextInt(SomaGene.MAX_OUTPUT_USAGE_ID));
					newAxonGene1 = new AxonGene(Brain.axonInnovNum++, oldAxonGene.getEndID(), newSomaGene.getID(),
							oldAxonGene.getWeight());
					newAxonGene2 = new AxonGene(Brain.axonInnovNum++, oldAxonGene.getStartID(), newSomaGene.getID(),
							oldAxonGene.getWeight());

				}
				// NEW HIDDEN
				else {
					newSomaGene = new SomaGene(Brain.somaInnovNum++, SomaGene.HIDDEN);
					// disable the original axon
					oldAxonGene.setDisabled(true);

					// increment the InnovNums when doing this!
					newAxonGene1 = new AxonGene(Brain.axonInnovNum++, oldAxonGene.getStartID(), newSomaGene.getID(),
							oldAxonGene.getWeight());
					newAxonGene2 = new AxonGene(Brain.axonInnovNum++, newSomaGene.getID(), oldAxonGene.getEndID(),
							oldAxonGene.getWeight());

				}
				// ADD TO DNA
				sChr.addGene(newSomaGene);
				aChr.addGene(newAxonGene1);
				aChr.addGene(newAxonGene2);

			}
		}

	}

	/*
	 * create a new axon by randomly generating one and inserting it into the
	 * network
	 * 
	 * The new axon will never be pointing to an input neuron Never duplicate
	 * axons
	 */
	private static void mutateNewAxon(SomaChromosome sChr, AxonChromosome aChr, MersenneTwisterFast random) {

		for (int counter = 0; counter < aChr.length(); counter++) {
			if (random.nextDouble() < Brain.NEW_AXON_MUT_RATE) {

				int startIndex, endIndex;
				int startID, endID;
				int tries = 0;
				boolean isGood;
				AxonGene newAxonGene;

				while (tries < 100) {

					startIndex = random.nextInt(sChr.length());
					endIndex = random.nextInt(sChr.length());

					// find a non-input node
					while (sChr.getGene(endIndex).getType() == SomaGene.INPUT
							|| sChr.getGene(endIndex).getType() == SomaGene.BIAS) {
						endIndex = random.nextInt(sChr.length());
					}

					startID = sChr.getGene(startIndex).getID();
					endID = sChr.getGene(endIndex).getID();

					isGood = true;

					// make sure link is does not already exist
					for (int i = 0; i < aChr.length(); i++) {
						if (aChr.getGene(i).getStartID() == startID && aChr.getGene(i).getEndID() == endID) {
							isGood = false;
							break;
						}
					}

					if (isGood) {
						newAxonGene = new AxonGene(Brain.axonInnovNum++, startID, endID, random);
						aChr.addGene(newAxonGene);
						break;
					} else {
						tries++;
					}

				}

			}
		}

	}

	/*
	 * returns true if the two brains are compatible for crossover
	 */
	public boolean isCompatible(Brain that) {

		double distance, avgWeightDiff;
		int somaExcess, axonExcess, totalExcess;
		int disjointAxons, disjointSomas, totalDisjoint;
		int countSameAxons = 0, countSameSomas = 0;
		;
		int thisIndex, thatIndex;
		SomaGene thisSomaGene, thatSomaGene;
		AxonGene thisAxonGene, thatAxonGene;

		// find number of disjoint axons as well as sum the weights of same ones
		thisIndex = thatIndex = 0;
		avgWeightDiff = 0.0;
		disjointAxons = Math.min(this.aChrome.length(), that.aChrome.length());

		while (thisIndex < this.aChrome.length() && thatIndex < that.aChrome.length()) {

			thisAxonGene = this.aChrome.getGene(thisIndex);
			thatAxonGene = that.aChrome.getGene(thatIndex);

			if (thisAxonGene.getID() == thatAxonGene.getID()) {

				countSameAxons++;
				disjointAxons--;

				avgWeightDiff += Math.abs(thisAxonGene.getWeight() - thatAxonGene.getWeight());

				thisIndex++;
				thatIndex++;

			} else if (thisAxonGene.getID() < thatAxonGene.getID()) {
				thisIndex++;
			} else {
				thatIndex++;
			}

		}

		axonExcess = Math.max(this.aChrome.length() - thisIndex, that.aChrome.length() - thatIndex);

		// go through the soma gense
		thisIndex = thatIndex = 0;
		disjointSomas = Math.min(this.sChrome.length(), that.sChrome.length());

		while (thisIndex < this.sChrome.length() && thatIndex < that.sChrome.length()) {

			thisSomaGene = this.sChrome.getGene(thisIndex);
			thatSomaGene = that.sChrome.getGene(thatIndex);

			if (thisSomaGene.getID() == thatSomaGene.getID()) {

				countSameSomas++;
				disjointSomas--;
				thisIndex++;
				thatIndex++;

			} else if (thisSomaGene.getID() < thatSomaGene.getID()) {
				thisIndex++;

			} else {
				thatIndex++;
			}

		}

		somaExcess = Math.max(this.sChrome.length() - thisIndex, that.sChrome.length() - thatIndex);

		totalDisjoint = disjointAxons + disjointSomas;

		if (countSameAxons == 0) {
			avgWeightDiff = 0.0;
		} else {
			avgWeightDiff /= countSameAxons;
		}

		totalExcess = somaExcess + axonExcess;

		distance = Brain.c1 * totalExcess + Brain.c2 * totalDisjoint + Brain.c3 * avgWeightDiff;

		return distance < Brain.compThreshold;

	}

	public static void addToCompThreshold(double d) {

		Brain.compThreshold += d;

		if (Brain.compThreshold < 0.0)
			Brain.compThreshold = 0.0;

	}

	public static double getCompatibilityThreshold() {
		return Brain.compThreshold;
	}

	public static void setCompatibilityThreshold(double d) {
		Brain.compThreshold = d;
	}

	public static void setCompatibility1(double d) {
		Brain.c1 = d;
	}

	public static void setCompatibility2(double d) {
		Brain.c2 = d;
	}

	public static void setCompatibility3(double d) {
		Brain.c3 = d;
	}

	public static void setAxonWeightMutRate(double d) {
		Brain.AXON_WEIGHT_MUT_RATE = d;
	}

	public static void setNewSomaMutRate(double d) {
		Brain.NEW_SOMA_MUT_RATE = d;
	}

	public static void setNewAxonMutRate(double d) {
		Brain.NEW_AXON_MUT_RATE = d;
	}

	public static void setRemoveSomaMutRate(float d) {
		Brain.REMOVE_SOMA_MUT_RATE = d;
	}

	// abstract functions
	public abstract double[] activate(double[] inv);

	public int getSomaChromeLen() {
		return this.sChrome.length();
	}

	public int getAxonChromeLen() {
		return this.aChrome.length();
	}

	public String toString() {

		String ret = new String();

		ret = "SomaCount: " + this.sChrome.length() + "\n";
		ret += "AxonCount: " + this.aChrome.length() + "\n";

		for (int i = 0; i < this.sChrome.length(); i++) {
			ret += sChrome.getGene(i).toString() + "\n";
		}

		for (int i = 0; i < this.aChrome.length(); i++) {
			ret += aChrome.getGene(i).toString() + "\n";
		}

		return ret;

	}

	public AxonChromosome getAxonChromosome(){
		return this.aChrome;
	}
	public SomaChromosome getSomaChromosome(){
		return this.sChrome;
	}
	public int getInputSize() {
		return inputNeurons.length;
	}

	public int getOutputSize() {
		return outputNeurons.length;
	}

	public int getSize() {
		return neurons.length;
	}

	public int getMaxInputID() {
		return maxInputID;
	}
	public Neuron[] getNeurons(){
		return neurons;
	}
	public Axon[] getAxons(){
		return allAxons;
	}

}
